package com.kusiri.kframework
{
	
	import com.kusiri.kframework.component.IKfComponent;
	import com.kusiri.kframework.component.KfComponentCollection;
	import com.kusiri.kframework.metadata.KfApplicationConfiguration;
	import com.kusiri.kframework.metadata.KfComponentConfiguration;
	import com.kusiri.kframework.metadata.KfComponentMetadata;
	import com.kusiri.kframework.metadata.KfComponentMetadataLocator;
	import com.kusiri.kframework.ui.*;
	
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	
	import mx.controls.Alert;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.managers.CursorManager;
	import mx.utils.ObjectUtil;
	import mx.utils.UIDUtil;
	
	/**
	 * The context for the KfApplication.
	 * 
	 * This class takes care of creating and initialising components,
	 * including tracking dependencies.
	 */
	public class KfContext implements IKfContext
	{
		/**
		 * 
		 */
		protected var _applicationConfigurationXml : XML;
		
		
		/**
		 * 
		 */
		protected var _applicationConfiguration : KfApplicationConfiguration;
		
		/**
		 * Component singletons
		 */
		protected var _componentSingletons : Dictionary;


		/**
		 * The KfApplication using the context
		 */		
		protected var _kfApplication : IKfApplication;
		
		/**
		 * The UI container
		 */
		protected var _uiContainer : IKfUiContainer;

		/**
		 * Logger
		 */
		protected var _logger:ILogger = Log.getLogger('com.kusiri.kframework.KfComponentManager');

		protected var _isInitialized : Dictionary;
		protected var _isInitializing  : Dictionary;
		protected var _failedToInitialize : Dictionary;
		protected var _onSuccessCallbacks : Dictionary;
		protected var _onFailCallbacks : Dictionary;

		/**
		 * 
		 */
		public function KfContext() : void
		{
			_kfApplication = new KfApplicationLocator().kfApplication;
		}
		
		/**
		 * Called to kick off setting up the application
		 */
		public function initialize( config:XML, callback:Function = null ) : void
		{	
			_applicationConfigurationXml = config;
			_applicationConfiguration = KfApplicationConfiguration.fromXml(_applicationConfigurationXml);
			
			_componentSingletons = new Dictionary(true);
			_isInitialized = new Dictionary(true);
			_isInitializing = new Dictionary(true);
			_failedToInitialize = new Dictionary(true);
			_onSuccessCallbacks = new Dictionary(true);
			_onFailCallbacks = new Dictionary(true);
			
			
//			var plugins:ArrayCollection = applicationContext.getObject(PLUGIN_SET_ID) as ArrayCollection;		
//			new KfPluginLoader().load(plugins, callback);
			
			if (callback != null) callback();

		}

		/**
		 * 
		 */
		public function get applicationConfiguration() : KfApplicationConfiguration
		{
			return _applicationConfiguration;
		}
		
		/**
		 * 
		 */
		public function getNewApplicationConfiguration() : KfApplicationConfiguration
		{
			return KfApplicationConfiguration.fromXml(_applicationConfigurationXml);
		}
		
		/**
		 * Starts the application by initializing the initial UI
		 */
		public function initializeInitialUi() : void
		{
			_uiContainer.enabled = false;
			
			var initalUiComponents : KfComponentCollection = new KfComponentCollection();
			var options : Object = {};
			
			var components : Array = _applicationConfiguration.initialUi.initialUi;
			
			for each ( var foo : Object in components )
			{
				var component : IKfComponent = getComponentWithoutInitializing(foo.uiComponent);
				options[UIDUtil.getUID(component)] = foo.uiComponentOptions;
				initalUiComponents.addItem(component);
			}
			
			initializeComponent( initalUiComponents,
			
				function (o:*) : void
				{
					for each ( var component : IKfUiComponent in initalUiComponents )
					{
						_uiContainer.addUiComponent(component, null, null, options[UIDUtil.getUID(component)]);
					}
					_uiContainer.enabled = true;
				},
				function (e:*) : void
				{
					Alert.show('Could initialize application');
				}
			);
			
		}
		
		/**
		 * 
		 */
		public function getComponentMetadata( clazz : String ) : KfComponentMetadata
		{
			return KfComponentMetadataLocator.getComponentMetadata(clazz);
		}
		
		/**
		 * The container of KfUiComponents
		 */
		public function get uiContainer() : IKfUiContainer
		{
			if (_uiContainer != null) return _uiContainer;
			
			_uiContainer = _applicationConfiguration.uiContainer.getNewUiContainer();
			
			return _uiContainer;
		}

		/**
		 * Get a component ready for use
		 */
		public function initializeComponent(component : IKfComponent, onInitialized : Function, onFail : Function) : void
		{
						
			_logger.debug('Initialising '+(component as Object));
			
			if (_isInitialized[component])
			{
				_logger.debug('Bailing: already initialised');
				onInitialized(component);
				return;
			}
			
			if (_failedToInitialize[component])
			{
				_logger.debug('Bailing: failed to initialise');
				onFail(component);
				return;
			}
			
			_onSuccessCallbacks[component]||=[];
			_onSuccessCallbacks[component].push(onInitialized);
			_onFailCallbacks[component]||=[];
			_onFailCallbacks[component].push(onFail);
			
			if (  _isInitializing[component] )
			{
				_logger.debug('Bailing: already initialising - added to call stack');
				return;
			}
			
			_kfApplication.waitingForSomething();

			_isInitializing[component] = true;
			
			
			var kfDependencyComponents : Array = [];
			
			if (component is KfComponentCollection) 
            {
            	(component as KfComponentCollection).source.forEach( function(element:*, index:int, arr:Array):void { kfDependencyComponents.push(element); } );
            } 
			
			// simple for..in loop doesn't work
			
            var classInfo:XML = describeType(component);

            // List the object's variables, their values, and their types.
            for each (var v:XML in classInfo..variable) 
            {
            	if (component[v.@name] is IKfComponent) 
				{
					kfDependencyComponents.push(component[v.@name]);
				}
            }

            // List accessors as properties.
            for each (var a:XML in classInfo..accessor) 
            {
            	var canRead : Boolean = a.@access != 'writeonly';
            	var userSettable : Boolean = a.@name.charAt()!='_';
            	if ( canRead && userSettable && component[a.@name] is IKfComponent) 
				{
					kfDependencyComponents.push(component[a.@name]);
            	}
            } 
			
			_logger.debug('Got '+kfDependencyComponents.length+' components to recursively initialise');
			
			// no dependencies?
			if (kfDependencyComponents.length == 0)
			{
				component.__initializeComponent(__initializationFinished, __initializationFailed);
				return;
			}
			
			var uninitializedInitialComponentCount:int = kfDependencyComponents.length;
			for each ( var c: IKfComponent in kfDependencyComponents)
			{
				_logger.debug('Recursively initialising '+(c as Object).toString());
				initializeComponent(c, 
					function(c:IKfComponent):void
					{
						_logger.debug('Recursively initialised '+(c as Object).toString());
						uninitializedInitialComponentCount--;
						if (uninitializedInitialComponentCount == 0)
						{
							// check we're still initialising it
							// and something else hasn't initialised it first
							if (_isInitializing[component])
							{
								component.__initializeComponent(__initializationFinished, __initializationFailed);
							}
						}
					}
					,
					function(c:IKfComponent, error:*):void
					{
						_isInitializing[component] = false;
						_isInitialized[component] = false;
						_kfApplication.finishedWaitingForSomething();
						Alert.show('Could not initialise component.');
						_logger.debug(ObjectUtil.toString(error));
						// app will remain disabled now, as uninitializedInitialComponentCount > 0
					}
				);
			}
			
		}

		/**
		 * Callback invoked by the component if init fails
		 */
		protected function __initializationFailed(component:IKfComponent, error:*=null) : void
		{
			_logger.debug(ObjectUtil.toString(error));
			
			if (component==null)
			{
				throw Error('Null component');
			}
			
			// remove any cursors
			CursorManager.removeAllCursors();			
			
			_logger.debug('Initialisation failed for '+component);
			
			_failedToInitialize[component] = true;
			_isInitializing[component] = false;
			
			// we've succeeded by design here
			for each ( var fn : Function in _onFailCallbacks[component])
			{
				fn(component, error);
			}
			
			delete _onSuccessCallbacks[component];
			delete _onFailCallbacks[component];
		}
		
		/**
		 * Callback invoked by the component if init succeeds
		 */
		protected function __initializationFinished(component:IKfComponent) : void
		{
			if (component==null)
			{
				throw Error('Null component');
			}
			
			_kfApplication.finishedWaitingForSomething();
			
			_logger.debug('Initialisation finished for '+component);
			
			_isInitialized[component] = true;
			_isInitializing[component] = false;
			
			// we've succeeded by design here
			for each ( var fn : Function in _onSuccessCallbacks[component])
			{
				fn(component);
			}
			
			delete _onSuccessCallbacks[component];
			delete _onFailCallbacks[component];
		}

		/**
		 * Get an component, initialising it for use
		 */
		public function getComponent(componentId:String, onSuccess : Function, onFail : Function) : void
		{	
			initializeComponent(getComponentWithoutInitializing(componentId), onSuccess, onFail);
		}
		
		/**
		 * This function gets a component by name, without initializing it (or its deps)
		 */
		public function getComponentWithoutInitializing(componentId:String) : IKfComponent
		{	

			var componentConfig : KfComponentConfiguration = _applicationConfiguration.getComponentConfiguration(componentId);
			if (componentConfig == null)
			{
				throw new Error('No such component '+componentId);
			}

			var isUiComponent : Boolean = componentConfig.metadata.isUiComponent;
			
			var isSingleton : Boolean = ! isUiComponent;
			
			if ( isSingleton )
			{
				var cachedComponent : IKfComponent = _componentSingletons[componentId];
				if (cachedComponent != null) 
				{
					return cachedComponent;
				}
			}
			
			var component : IKfComponent = componentConfig.getNewInstance(this);
			
			if ( isSingleton ) 
			{
				_componentSingletons[componentId] = component;
			}

			return component;
		
		}

	}
}